#include "transmit.h"
#include "flash_table.h"
#include "vehicle.h"
#include "process.h"
#include "gps.h"

#define DEVICE_ID                       transmit_get_deviceid()  //"6000000000000040"

#define KEY_CONTROL_WINDOW              "bcm_fun_window"
#define KEY_CONTROL_DOOR                "bcm_fun_door"
#define KEY_CONTROL_LIGHT               "bcm_fun_light"
#define KEY_CONTROL_SUNROOF             "bcm_fun_sunroof"
#define KEY_CONTROL_TRUNK               "bcm_fun_trunk"
#define KEY_CONTROL_FINDCAR             "bcm_fun_findcar"
#define KEY_CONTROL_IMMOLOCK            "bcm_fun_immolock"

#define KEY_ENG_RPM                     "eng_data_rpm"
#define KEY_ENG_VS                      "eng_data_vs"
#define KEY_ENG_ECT                     "eng_data_ect"
#define KEY_ENG_IAT                     "eng_data_iat"
#define KEY_ENG_APP                     "eng_data_app"
#define KEY_ENG_TP                      "eng_data_tp"
#define KEY_ENG_ERT                     "eng_data_ert"
#define KEY_ENG_LOAD                    "eng_data_load"
#define KEY_ENG_BATTVOLT                "bcm_data_battvolt"
#define KEY_ENG_REALFUELCO              "eng_data_realfuelco"
#define KEY_ENG_FUELLEVEL               "eng_data_fuellevel"
#define KEY_ENG_ODO                     "bcm_data_odo"
#define KEY_ENG_LTFT                    "eng_data_ltft"
#define KEY_ENG_STFT                    "eng_data_stft"
#define KEY_ENG_MISFIRE1                "eng_data_misfire1"
#define KEY_ENG_MISFIRE2                "eng_data_misfire2"
#define KEY_ENG_MISFIRE3                "eng_data_misfire3"
#define KEY_ENG_MISFIRE4                "eng_data_misfire4"
#define KEY_ENG_MISFIRE5                "eng_data_misfire5"
#define KEY_ENG_MISFIRE6                "eng_data_misfire6"
#define KEY_ENG_FCLS                    "eng_data_fcls"
#define KEY_ENG_KEYSTATUS               "eng_data_keystatus"
#define KEY_ENG_HO2S1                   "eng_data_ho2s1"
#define KEY_ENG_HO2S2                   "eng_data_ho2s2"
#define KEY_ENG_MAP                     "eng_data_map"
#define KEY_ENG_INJECTPULSE             "eng_data_injectpulse"
#define KEY_ENG_OILPRESSURE             "eng_data_oilpressure"
#define KEY_ENG_OILLEVELSTATUS          "eng_data_oillevelstatus"

#define KEY_IPC_REALFUELCO              "eng_data_realfuelco"
#define KEY_IPC_ODO                     "bcm_data_odo"
#define KEY_IPC_FUELLEVEL               "eng_data_fuellevel"

#define KEY_BCM_LFWIN                   "bcm_data_lfwin"
#define KEY_BCM_RFWIN                   "bcm_data_rfwin"
#define KEY_BCM_LRWIN                   "bcm_data_lrwin"
#define KEY_BCM_RRWIN                   "bcm_data_rrwin"
#define KEY_BCM_DDA                     "bcm_data_dda"
#define KEY_BCM_PDA                     "bcm_data_pda"
#define KEY_BCM_RRDA                    "bcm_data_rrda"
#define KEY_BCM_LRDA                    "bcm_data_lrda"
#define KEY_BCM_SUNROOF                 "bcm_data_sunroof"
#define KEY_BCM_PARKLAMP                "bcm_data_parklamp"
#define KEY_BCM_HEADLAMP                "bcm_data_headlamp"
#define KEY_BCM_CHARGESTATUS            "bcm_data_chargestatus"
#define KEY_BCM_BATTCURRENT             "bcm_data_battcurrent"
#define KEY_BCM_BATTSTATUS              "bcm_data_battstatus"
#define KEY_BCM_BATTVOLT                "bcm_data_battvolt"
#define KEY_BCM_ODO                     "bcm_data_odo"

#define KEY_ENGINE_CODE                 "powertrain"
#define KEY_AT_CODE                     "powertrain"
#define KEY_ABS_CODE                    "chassis"
#define KEY_SRS_CODE                    "body_system"
#define KEY_BCM_CODE                    "body_system"
#define KEY_IPC_CODE                    "body_system"
#define KEY_EPS_CODE                    "chassis"
#define KEY_AC_CODE                     "body_system"
#define KEY_TPMS_CODE                   "body_system"


#define HEARTBEAT_RSP_TIMEOUT            8
#define LOGIN_RSP_TIMEOUT                6
#define HEARTBEAT_FAIL_TOLERENT          3
#define LOGIN_FAIL_TOLERENT              8

#define MODEL_TOYOTA                     "toyota"
#define MODEL_GM                         "gm"
#define MODEL_VAG                        "vag1"
#define MODEL_LAVIDA                     "vag"
#define MODEL_HAVAL                      "greatwall"
#define MODEL_HYUNDAI                    "hyundaibj"
#define MODEL_SENOVA                     "senova"
#define MODEL_EOBD                       "eobd"

#define UPLOAD_MAX_ITEMS                 10

#define TIMEZONE                         8

uint16_t mThisYear = 2017;

CtrlItem ctrlTable[CONTROL_END] =
{
    {CONTROL_WINDOW,    KEY_CONTROL_WINDOW},
    {CONTROL_DOOR,      KEY_CONTROL_DOOR},
    {CONTROL_LIGHT,     KEY_CONTROL_LIGHT},
    {CONTROL_SUNROOF,   KEY_CONTROL_SUNROOF},
    {CONTROL_TRUNK,     KEY_CONTROL_TRUNK},
    {CONTROL_FINDCAR,   KEY_CONTROL_FINDCAR},
    {CONTROL_IMMOLOCK,  KEY_CONTROL_IMMOLOCK},
};

PidItem pidList[PID_SIZE] =
{
    {ENG_DATA_RPM,              KEY_ENG_RPM},
    {ENG_DATA_VS,               KEY_ENG_VS},
    {ENG_DATA_ECT,              KEY_ENG_ECT},
    {ENG_DATA_IAT,              KEY_ENG_IAT},
    {ENG_DATA_APP,              KEY_ENG_APP},
    {ENG_DATA_TP,               KEY_ENG_TP},
    {ENG_DATA_ERT,              KEY_ENG_ERT},
    {ENG_DATA_LOAD,             KEY_ENG_LOAD},
    {ENG_DATA_BATTVOLT,         KEY_ENG_BATTVOLT},
    {ENG_DATA_REALFUELCO,       KEY_ENG_REALFUELCO},
    {ENG_DATA_FUELLEVEL,        KEY_ENG_FUELLEVEL},
    {ENG_DATA_ODO,              KEY_ENG_ODO},
    {ENG_DATA_LTFT,             KEY_ENG_LTFT},
    {ENG_DATA_STFT,             KEY_ENG_STFT},
    {ENG_DATA_MISFIRE1,         KEY_ENG_MISFIRE1},
    {ENG_DATA_MISFIRE2,         KEY_ENG_MISFIRE2},
    {ENG_DATA_MISFIRE3,         KEY_ENG_MISFIRE3},
    {ENG_DATA_MISFIRE4,         KEY_ENG_MISFIRE4},
    {ENG_DATA_MISFIRE5,         KEY_ENG_MISFIRE5},
    {ENG_DATA_MISFIRE6,         KEY_ENG_MISFIRE6},
    {ENG_DATA_FCLS,             KEY_ENG_FCLS},
    {ENG_DATA_KEYSTATUS,        KEY_ENG_KEYSTATUS},
    {ENG_DATA_HO2S1,            KEY_ENG_HO2S1},
    {ENG_DATA_HO2S2,            KEY_ENG_HO2S2},
    {ENG_DATA_MAP,              KEY_ENG_MAP},
    {ENG_DATA_INJECTPULSE,      KEY_ENG_INJECTPULSE},
    {ENG_DATA_OILPRESSURE,      KEY_ENG_OILPRESSURE},
    {ENG_DATA_OILLEVELSTATUS,   KEY_ENG_OILLEVELSTATUS},
};

PidItem bcmList[BCM_DATA_SIZE] =
{
    {BCM_DATA_LFWIN,            KEY_BCM_LFWIN},
    {BCM_DATA_RFWIN,            KEY_BCM_RFWIN},
    {BCM_DATA_LRWIN,            KEY_BCM_LRWIN},
    {BCM_DATA_RRWIN,            KEY_BCM_RRWIN},
    {BCM_DATA_DDA,              KEY_BCM_DDA},
    {BCM_DATA_PDA,              KEY_BCM_PDA},
    {BCM_DATA_RRDA,             KEY_BCM_RRDA},
    {BCM_DATA_LRDA,             KEY_BCM_LRDA},
    {BCM_DATA_SUNROOF,          KEY_BCM_SUNROOF},
    {BCM_DATA_PARKLAMP,         KEY_BCM_PARKLAMP},
    {BCM_DATA_HEADLAMP,         KEY_BCM_HEADLAMP},
    {BCM_DATA_CHARGESTATUS,     KEY_BCM_CHARGESTATUS},
    {BCM_DATA_BATTCURRENT,      KEY_BCM_BATTCURRENT},
    {BCM_DATA_BATTSTATUS,       KEY_BCM_BATTSTATUS},
    {BCM_DATA_BATTVOLT,         KEY_BCM_BATTVOLT},
    {BCM_DATA_ODO,              KEY_BCM_ODO},
};

PidItem ipcList[IPC_DATA_SIZE] =
{
    {IPC_DATA_REALFUELCO,       KEY_IPC_REALFUELCO},
    {IPC_DATA_ODO,              KEY_IPC_ODO},
    {IPC_DATA_FUELLEVEL,        KEY_IPC_FUELLEVEL},
};

//login & heartbeat
OS_TCB HeartbeatTaskTCB;
CPU_STK HEARTBEAT_TASK_STK[TRANSMIT_HEARTBEAT_TASK_STK_SIZE];
#define HEARTBEAT_INTERVAL              10
#define UPLOAD_INTERVAL                 180
#define PROCESS_QUEUE_SIZE              10
#define PROCESS_PENDING_TIMEOUT         5
//receive cmd from gprs module
OS_TCB TransmitProcessTaskTCB;
CPU_STK TRANSMIT_PROCESS_TASK_STK[TRANSMIT_PROCESS_TASK_STK_SIZE];
//upload car data to server
OS_TCB UploadTaskTCB;
CPU_STK UPLOAD_TASK_STK[TRANSMIT_UPLOAD_TASK_STK_SIZE];

char mDeviceId[DEVICE_ID_LEN];

extern OS_TCB VehicleControlTaskTCB;
extern EngineDataMap mEngDataMap;
extern BcmDataMap mBcmDataMap;
extern IpcDataMap mIpcDataMap;
extern VinCode mVinCode;

void transmit_epo_fragment(char *version, uint32_t offset, uint32_t limit);

Transmission mTransmit;
uint8_t mFirstGpsDone = FALSE;
uint8_t mLogined = FALSE;

FixTime mLoginTime;
char mEpoVersion[32];
uint32_t mEpoFileSize;
uint32_t mEpoFileGpsHour;

#define MTKEPO_SV_NUMBER            32
#define MTKEPO_RECORD_SIZE          72
#define MTKEPO_SEGMENT_NUM          120

uint8_t mEpoBuffer[MTKEPO_RECORD_SIZE];
uint8_t mEpoProcessStage = 0;

#define PLUGOUT_TIME_LIMIT      10

void parse_login_time(FixTime *time, char *buf)
{
    uint16_t year;
    uint16_t last_year;
    uint8_t len, i;
    uint8_t mon, day, hour, min, sec;
    uint8_t last_mon, last_day, last_hour, last_min;
    uint8_t num[2];
    int thisMinutes = 0;
    int lastMinutes = 0;

    char save_time[20];
    char last_time[20];

    memcpy(save_time, buf, 19);
    save_time[19] = '\0';
    logi("%s: current %s", __func__, buf);
    iFlash_get_login_time(last_time);
    logi("%s: saved   %s", __func__, last_time);

    len = strlen(buf);
    if(len != 19) {
        loge("%s: login time is valid", __func__);
        return;
    }
    memset(num, 0x00, 2);
    //year
    num[0] = buf[2];
    num[1] = buf[3];
    year = 2000 + atoi(num);
    //month
    num[0] = buf[5];
    num[1] = buf[6];
    mon = atoi(num);
    //day
    num[0] = buf[8];
    num[1] = buf[9];
    day = atoi(num);
    //hour
    num[0] = buf[11];
    num[1] = buf[12];
    hour = atoi(num);
    //minute
    num[0] = buf[14];
    num[1] = buf[15];
    min = atoi(num);
    //minute
    num[0] = buf[17];
    num[1] = buf[18];
    sec = atoi(num);
    logi("%s: login time: %d-%d-%d %d:%d",
            __func__, year, mon, day, hour, min);
    time->year = year;
    time->mon = mon;
    time->day = day;
    time->hour = hour;
    time->min = min;
    time->sec = sec;
    //save login time to flash
    iFlash_set_login_time(save_time);
    //convert to utc time
    //if(hour < 8) {
    //    day --;
    //    hour = 24 + hour - 8;
    //} else {
    //    hour -= 8;
    //}
    //gps_set_utc(year, mon, day, hour, min, sec + 1);
    mThisYear = year;
    thisMinutes = hour * 60 + min;
    //year
    num[0] = last_time[2];
    num[1] = last_time[3];
    last_year = 2000 + atoi(num);
    //month
    num[0] = last_time[5];
    num[1] = last_time[6];
    last_mon = atoi(num);
    //day
    num[0] = last_time[8];
    num[1] = last_time[9];
    last_day = atoi(num);
    //hour
    num[0] = last_time[11];
    num[1] = last_time[12];
    last_hour = atoi(num);
    //minute
    num[0] = last_time[14];
    num[1] = last_time[15];
    last_min = atoi(num);
    logi("%s:  last time: %d-%d-%d %d:%d",
            __func__, last_year, last_mon,
            last_day, last_hour, last_min);
    lastMinutes = last_hour * 60 + last_min;
    logi("%s: thisMinutes = %d", __func__, thisMinutes);
    logi("%s: lastMinutes = %d", __func__, lastMinutes);
    if(year != last_year ||
            mon != last_mon ||
            day != last_day) {
        if((thisMinutes - lastMinutes) < PLUGOUT_TIME_LIMIT ||
                (thisMinutes - lastMinutes) > 0) {
            //set lock
            //set_lock();
            //bsp_can_enable(FALSE);
        }
    }
    if((thisMinutes - lastMinutes) < PLUGOUT_TIME_LIMIT ||
            (thisMinutes - lastMinutes) > 0) {
        //set lock
        if(getImmoChanged() == TRUE) {
            set_unlock();
            bsp_can_enable(TRUE);
        }
    }
}

int get_gps_hour(int year, int mon,
        int day, int hour)
{
    int yearElapsed;
    int dayElapsed;
    int leapDays;
    int i;
    const unsigned short doy[12] = {0,31,59,90,120,151,181,212,243,273,304,334};

    yearElapsed = year - 1980;
    i = 0;
    leapDays = 0;

    while(i <= yearElapsed) {
        if((i % 100) == 20) {
            if((i % 400) == 20) {
                leapDays ++;
            }
        } else if((i % 4) == 0) {
            leapDays ++;
        }
        i++;
    }

    if((yearElapsed % 100) == 20) {
        if(((yearElapsed % 400) == 20) && (mon <= 2)) {
            leapDays --;
        }
    } else if(((yearElapsed % 4) == 0) && (mon <= 2)) {
        leapDays --;
    }

    dayElapsed = yearElapsed * 365 + (int)doy[mon - 1] + day + leapDays - 6;

    return (dayElapsed * 24 + hour);
}

void transmit_init(void)
{
    OS_ERR err;

    mTransmit.heartbeat = 0;
    mTransmit.send = l206_send;
    OSMutexCreate(
            (OS_MUTEX       *)&mTransmit.mutex,
            (CPU_CHAR       *)"TRANSMIT MUTEX",
            &err
            );
    //process task
    OSTaskCreate(
            (OS_TCB     *) &TransmitProcessTaskTCB,
            (CPU_CHAR   *) "Transmit Process Task",
            (OS_TASK_PTR ) transmit_process_task,
            (void       *) 0,
            (OS_PRIO     ) TRANSMIT_PROCESS_TASK_PRIO,
            (CPU_STK    *) &TRANSMIT_PROCESS_TASK_STK[0],
            (CPU_STK_SIZE) TRANSMIT_PROCESS_TASK_STK_SIZE / 10,
            (CPU_STK_SIZE) TRANSMIT_PROCESS_TASK_STK_SIZE,
            (OS_MSG_QTY  ) PROCESS_QUEUE_SIZE,
            (OS_TICK     ) 0,
            (void       *) 0,
            (OS_OPT      ) OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR,
            (OS_ERR     *) &err
            );
    //heartbeat task
    OSTaskCreate(
            (OS_TCB     *) &HeartbeatTaskTCB,
            (CPU_CHAR   *) "Heartbeat Task",
            (OS_TASK_PTR ) transmit_heartbeat_task,
            (void       *) 0,
            (OS_PRIO     ) TRANSMIT_HEARTBEAT_TASK_PRIO,
            (CPU_STK    *) &HEARTBEAT_TASK_STK[0],
            (CPU_STK_SIZE) TRANSMIT_HEARTBEAT_TASK_STK_SIZE / 10,
            (CPU_STK_SIZE) TRANSMIT_HEARTBEAT_TASK_STK_SIZE,
            (OS_MSG_QTY  ) 0,
            (OS_TICK     ) 0,
            (void       *) 0,
            (OS_OPT      ) OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR,
            (OS_ERR     *) &err
            );
    //upload task
    OSTaskCreate(
            (OS_TCB     *) &UploadTaskTCB,
            (CPU_CHAR   *) "Upload Task",
            (OS_TASK_PTR ) transmit_upload_task,
            (void       *) 0,
            (OS_PRIO     ) TRANSMIT_UPLOAD_TASK_PRIO,
            (CPU_STK    *) &UPLOAD_TASK_STK[0],
            (CPU_STK_SIZE) TRANSMIT_UPLOAD_TASK_STK_SIZE / 10,
            (CPU_STK_SIZE) TRANSMIT_UPLOAD_TASK_STK_SIZE,
            (OS_MSG_QTY  ) 0,
            (OS_TICK     ) 0,
            (void       *) 0,
            (OS_OPT      ) OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR,
            (OS_ERR     *) &err
            );
    OSTaskSuspend(&UploadTaskTCB, &err);
    mFirstGpsDone = FALSE;
    memset(&mLoginTime, 0x00, sizeof(FixTime));
    mLogined = FALSE;
    mEpoProcessStage = 0;
}

static void transmit_process_task(void *parg)
{
    OS_ERR err;
    OS_MSG_SIZE size;
    uint8_t *rx_buf;
    uint8_t i;
    cJSON *json, *item;
    uint32_t cmd_id, ctrl_val;
    char *tmp;
    uint8_t msg_type;
    uint8_t heartbeat_rsp;
    CtrlMsg ctrlMsg;
    char *fragment;
    uint8_t utc[4];
    uint8_t frag_size;

    memset(&ctrlMsg, 0x00, sizeof(ctrlMsg));
    while(1) {
        //wait for queue post
        rx_buf = OSTaskQPend(
                (OS_TICK)0/*(OS_CFG_TICK_RATE_HZ * PROCESS_PENDING_TIMEOUT)*/,
                (OS_OPT)OS_OPT_PEND_BLOCKING,
                (OS_MSG_SIZE*)&size,
                (CPU_TS*)0,
                (OS_ERR*)&err
                );
        if(err == OS_ERR_TIMEOUT) {
            logi("%s: timeout", __func__);
            continue;
        }
        logi("%s: %s", __func__, rx_buf);
        json = cJSON_Parse((const char *)rx_buf);
        if(!json) {
            loge("[%s]", cJSON_GetErrorPtr());
        } else {
            //get msg_type
            item = cJSON_GetObjectItem(json, KEY_MSG_TYPE);
            if(item) {
                msg_type = item->valueint;
            } else {
                loge("%s: failed to get msg_type", __func__);
                continue;
            }
            switch(msg_type) {
                case MSG_TYPE_HEARTBEAT_RSP:
                    item = cJSON_GetObjectItem(json, KEY_HEARTBEAT);
                    if(item) {
                        heartbeat_rsp = item->valueint;
                        if(((mTransmit.heartbeat - 1) * 2 + 1) == heartbeat_rsp) {
                            //post heart response
                            OSTaskSemPost(&HeartbeatTaskTCB,
                                    OS_OPT_POST_NONE, &err);
                        } else {
                            loge("failed to get heartbeat response");
                        }
                    }
                    break;
                case MSG_TYPE_CTRL:
#ifdef SERVER_IS_K
                    item = cJSON_GetObjectItem(json, KEY_CMD_ID);
                    cmd_id = item->valueint;
#endif
                    for(i = 0; i < CONTROL_END; i++) {
                        tmp = strstr((char *)rx_buf, ctrlTable[i].key);
                        if(tmp) {
                            item = cJSON_GetObjectItem(json,
                                    ctrlTable[i].key);
#ifdef SERVER_IS_K
                            ctrl_val = item->valueint;
                            ctrlMsg.id = i;
                            ctrlMsg.cmd_id = cmd_id;
                            ctrlMsg.value = ctrl_val;
#elif defined SERVER_IS_VEHICLE_UNION
                            ctrlMsg.id = i;
                            ctrlMsg.cmd_id = 0;
                            if(!strcmp(item->valuestring, "ON")) {
                                ctrlMsg.value = 1;
                            } else if(!strcmp(item->valuestring, "OFF")) {
                                ctrlMsg.value = 0;
                            }
#endif
                            logi("%s: id = %d cmd_id = %d value = %d",
                                    ctrlTable[i].key,
                                    ctrlMsg.id,
                                    ctrlMsg.cmd_id,
                                    ctrlMsg.value);
                            //post ctrl msg to vehilce task
                            OSTaskQPost(
                                    (OS_TCB *)&VehicleControlTaskTCB,
                                    (void *)&ctrlMsg,
                                    (OS_MSG_SIZE)sizeof(CtrlMsg),
                                    (OS_OPT)OS_OPT_POST_FIFO,
                                    (OS_ERR *)&err
                                    );
                        }
                    }
                    break;
                case MSG_TYPE_CLEAR_FAULT:
                    //TODO: ???
                    logi("do clear fault");
                    break;
                case MSG_TYPE_VEHICLE_TYPE:
                    item = cJSON_GetObjectItem(json, KEY_LOGIN_TIME);
                    parse_login_time(&mLoginTime, item->valuestring);
                    item = cJSON_GetObjectItem(json, KEY_VEHICLE_TYPE);
                    if(!strcmp(item->valuestring, MODEL_TOYOTA)) {
                        logi("##TOYOTA##");
                        //setup toyota
                        vehicle_setup(VEHICLE_TOYOTA);
                    } else if(!strcmp(item->valuestring, MODEL_GM)) {
                        logi("##GM##");
                        //setup gm
                        vehicle_setup(VEHICLE_GM);
                    } else if(!strcmp(item->valuestring, MODEL_VAG)) {
                        logi("##VAG1##");
                        //setup vag
                        vehicle_setup(VEHICLE_VAG);
                    } else if(!strcmp(item->valuestring, MODEL_LAVIDA)) {
                        logi("##VAG##");
                        //setup vag
                        vehicle_setup(VEHICLE_LAVIDA);
                    } else if(!strcmp(item->valuestring, MODEL_HAVAL)) {
                        logi("##HAVAL##");
                        //setup haval
                        vehicle_setup(VEHICLE_HAVAL);
                    } else if(!strcmp(item->valuestring, MODEL_HYUNDAI)) {
                        logi("##HYUNDAI##");
                        //setup hyundai
                        vehicle_setup(VEHICLE_HYUNDAI);
                    } else if(!strcmp(item->valuestring, MODEL_SENOVA)) {
                        logi("##SENOVA##");
                        //setup hyundai
                        vehicle_setup(VEHICLE_SENOVA);
                    }
                    else if(!strcmp(item->valuestring, MODEL_EOBD)) {
                        logi("##EOBD##");
                        //setup eobd
                        vehicle_setup(VEHICLE_EOBD);
                    }
                    else {
                        logi("##EOBD##");
                        //setup eobd
                        vehicle_setup(VEHICLE_EOBD);
                    }
                    //post vehicle response
                    OSTaskSemPost(&HeartbeatTaskTCB,
                            OS_OPT_POST_NONE, &err);
                    break;

                case MSG_TYPE_CHECK_EPO_RSP:
                    logi("check epo rsp");
                    item = cJSON_GetObjectItem(json, "size");
                    mEpoFileSize = item->valueint;
                    item = cJSON_GetObjectItem(json, "version");
                    memset(mEpoVersion, 0x00, 32);
                    memcpy(mEpoVersion, item->valuestring, strlen(item->valuestring));
                    OSTaskSemPost(&HeartbeatTaskTCB,
                            OS_OPT_POST_NONE, &err);
                    break;
                case MSG_TYPE_FRAGMENT_RSP:
                    item = cJSON_GetObjectItem(json, "fragment");
                    fragment = item->valuestring;
                    logi("rsp fragment = %s", fragment);

                    if(mEpoProcessStage == 0) {
                        frag_size = base64Decode(utc, fragment);
                        logi("size = %d, %02x,%02x,%02x,%02x", frag_size,
                                utc[0], utc[1], utc[2], utc[3]);
                        mEpoFileGpsHour = utc[2] << 16;
                        mEpoFileGpsHour |= utc[1] << 8;
                        mEpoFileGpsHour |= utc[0];
                    } else {
                        frag_size = base64Decode(mEpoBuffer, fragment);
                        logi("size = %d", frag_size);
                    }
                    OSTaskSemPost(&HeartbeatTaskTCB,
                            OS_OPT_POST_NONE, &err);

                    break;
                default:
                    break;
            }
            cJSON_Delete(json);
        }
    }
}


void process_epo_buf(uint8_t index)
{
    //write buf into gps
    if(mEpoBuffer[3] != 0x00) {
        gps_aiding(index, mEpoBuffer);
    }
}

static void transmit_process_epo(void)
{
    OS_ERR err;
    uint32_t currentGpsHour;
    int32_t segment;
    uint32_t offset;
    uint16_t i;

    transmit_check_epo();
    OSTaskSemPend(
            OS_CFG_TICK_RATE_HZ * LOGIN_RSP_TIMEOUT,
            OS_OPT_PEND_BLOCKING,
            0,
            &err
            );
    if(err == OS_ERR_TIMEOUT) {
        loge("%s: timeout", __func__);
        return;
    } else {
        logi("%s: version: %s, file size = %d", __func__, mEpoVersion, mEpoFileSize);
        transmit_epo_fragment(mEpoVersion, 0, 4);
    }
    OSTaskSemPend(
            OS_CFG_TICK_RATE_HZ * LOGIN_RSP_TIMEOUT,
            OS_OPT_PEND_BLOCKING,
            0,
            &err
            );
    if(err == OS_ERR_TIMEOUT) {
        loge("%s: timeout", __func__);
        return;
    } else {
        mEpoProcessStage = 1;
        currentGpsHour = get_gps_hour(mLoginTime.year,
                mLoginTime.mon, mLoginTime.day, mLoginTime.hour) - 8;
        logi("current gps hour = %d", currentGpsHour);
        segment = (currentGpsHour - mEpoFileGpsHour) / 6;
        logi("segment = %d", segment);
        if(segment < 0 || (segment >= MTKEPO_SEGMENT_NUM)) {
            loge("segment invalid");
            return;
        }
        offset = segment * MTKEPO_RECORD_SIZE * MTKEPO_SV_NUMBER;
        logi("offset = %d", offset);

        for(i = 0; i < MTKEPO_SV_NUMBER; i++) {
            transmit_epo_fragment(mEpoVersion,
                    offset + i * MTKEPO_RECORD_SIZE, MTKEPO_RECORD_SIZE);
            OSTaskSemPend(
                    OS_CFG_TICK_RATE_HZ * LOGIN_RSP_TIMEOUT,
                    OS_OPT_PEND_BLOCKING,
                    0,
                    &err
                    );
            if(err == OS_ERR_TIMEOUT) {
                loge("%s: timeout", __func__);
                return;
            } else {
                process_epo_buf(i);
            }
        }
    }
}

static void transmit_heartbeat_task(void *parg)
{
    OS_ERR err;
    static uint8_t heartbeat_fail_times = 0;
    static uint8_t login_fail_times = 0;
    uint8_t is_login = FALSE;
    static Location location;

    while(!is_login) {
        logi("###login");
        os_delay(2);
        transmit_login();
        OSTaskSemPend(
                OS_CFG_TICK_RATE_HZ * LOGIN_RSP_TIMEOUT,
                OS_OPT_PEND_BLOCKING,
                0,
                &err
                );
        if(err == OS_ERR_TIMEOUT) {
            loge("##wait login timeout");
            if( ++login_fail_times > LOGIN_FAIL_TOLERENT) {
                login_fail_times = 0;
                loge("##SYSTEM REBOOT##");
                software_reboot();
            }
        } else {
            logi("get login response");
            login_fail_times = 0;
            is_login = TRUE;
            //resume the upload task
            OSTaskResume(&UploadTaskTCB, &err);
        }
    }
    mLogined = is_login;
    //process the epo files
    //remove this function
    //transmit_process_epo();
    while(1) {
        os_delay(HEARTBEAT_INTERVAL);
        mTransmit.heartbeat = (mTransmit.heartbeat == 100) ? 0
            : mTransmit.heartbeat;
        transmit_heartbeat(mTransmit.heartbeat++);
        //wait for heartbeat response
        OSTaskSemPend(
                OS_CFG_TICK_RATE_HZ * HEARTBEAT_RSP_TIMEOUT,
                OS_OPT_PEND_BLOCKING,
                0,
                &err
                );
        if(err == OS_ERR_TIMEOUT) {
            loge("##wait heartbeat timeout");
            if(++heartbeat_fail_times > HEARTBEAT_FAIL_TOLERENT) {
                heartbeat_fail_times = 0;
                loge("##SYSTEM REBOOT##");
                software_reboot();
            }
        } else {
            logi("get heartbeat response");
            heartbeat_fail_times = 0;
        }

        if((mTransmit.heartbeat % 6 == 0
                    && (process_check_engine() == TRUE))
                || mFirstGpsDone == FALSE) {
            if(gps_get_location(&location)) {
                //mFirstGpsDone = TRUE;
                //transmit_location(&location);
            } else {
                loge("gps data failed, change to agps");
                if(l206_get_location(&location)) {
                    mFirstGpsDone = TRUE;
                    transmit_location(&location);
                }
            }
        }
    }
}

static void transmit_upload_task(void *parg)
{
    while(1) {
        os_delay_min(UPLOAD_INTERVAL / 60);
        transmit_vin_code(&mVinCode);

        while(transmit_eng_data(&mEngDataMap) == TRUE) {
            os_delay(1);
        }

        while(transmit_bcm_data(&mBcmDataMap) == TRUE) {
            os_delay(1);
        }

        while(transmit_ipc_data(&mIpcDataMap) == TRUE) {
            os_delay(1);
        }
    }
}

void transmit_check_epo(void)
{
    cJSON *root = cJSON_CreateObject();
    char *out;
    uint16_t length;

    cJSON_AddStringToObject(root, KEY_DEVICE_ID, DEVICE_ID);
    cJSON_AddNumberToObject(root, KEY_MSG_TYPE, MSG_TYPE_CHECK_EPO);

    out = cJSON_Print(root);
    length = strlen(out);
    mTransmit.send((uint8_t *)out, length);
    cJSON_Delete(root);
    free(out);
}

void transmit_login(void)
{
    cJSON *root = cJSON_CreateObject();
    char *out;
    uint16_t length;

    cJSON_AddStringToObject(root, KEY_DEVICE_ID, DEVICE_ID);
    cJSON_AddNumberToObject(root, KEY_MSG_TYPE, MSG_TYPE_LOGIN);

    out = cJSON_Print(root);
    length = strlen(out);
    mTransmit.send((uint8_t *)out, length);
    cJSON_Delete(root);
    free(out);
}

void transmit_heartbeat(uint8_t count)
{
    cJSON *root = cJSON_CreateObject();
    char *out;
    uint16_t length;

    cJSON_AddStringToObject(root, KEY_DEVICE_ID, DEVICE_ID);
    cJSON_AddNumberToObject(root, KEY_MSG_TYPE, MSG_TYPE_HEARTBEAT);
    cJSON_AddNumberToObject(root, KEY_HEARTBEAT, count);

    out = cJSON_Print(root);
    length = strlen(out);
    mTransmit.send((uint8_t *)out, length);
    cJSON_Delete(root);
    free(out);
}

void transmit_control_rsp(uint32_t cmd_id, uint8_t id)
{
    cJSON *root = cJSON_CreateObject();
    char *out;
    uint16_t length;

    cJSON_AddStringToObject(root, KEY_DEVICE_ID, DEVICE_ID);
    cJSON_AddNumberToObject(root, KEY_MSG_TYPE, MSG_TYPE_CTRL_RSP);
#ifdef SERVER_IS_K
    cJSON_AddNumberToObject(root, KEY_CMD_ID, cmd_id);
#endif
    cJSON_AddNumberToObject(root, KEY_STATUS, 0);
    cJSON_AddStringToObject(root, KEY_CMD_TYPE, ctrlTable[id].key);

    out = cJSON_Print(root);
    length = strlen(out);
    mTransmit.send((uint8_t *)out, length);

    cJSON_Delete(root);
    free(out);
}

void transmit_epo_fragment(char *version, uint32_t offset, uint32_t limit)
{
    cJSON *root = cJSON_CreateObject();
    char *out;
    uint16_t length;

    cJSON_AddStringToObject(root, KEY_DEVICE_ID, DEVICE_ID);
    cJSON_AddNumberToObject(root, KEY_MSG_TYPE, 14);
    cJSON_AddNumberToObject(root, "offset", offset);
    cJSON_AddNumberToObject(root, "limit", limit);
    cJSON_AddStringToObject(root, "version", version);

    out = cJSON_Print(root);
    length = strlen(out);
    mTransmit.send((uint8_t *)out, length);

    cJSON_Delete(root);
    free(out);
}

void transmit_location(Location *location)
{
    cJSON *root = cJSON_CreateObject();
    char *out;
    uint16_t length;

    cJSON_AddStringToObject(root, KEY_DEVICE_ID, DEVICE_ID);
    cJSON_AddNumberToObject(root, KEY_MSG_TYPE, MSG_TYPE_LOCATION);
    cJSON_AddNumberToObject(root, KEY_LNG, location->longitude);
    cJSON_AddNumberToObject(root, KEY_LAT, location->latitude);

    out = cJSON_Print(root);
    length = strlen(out);
    mTransmit.send((uint8_t *)out, length);

    cJSON_Delete(root);
    free(out);
}

void transmit_multi_location(nmea_msg *msg, uint8_t count)
{
    cJSON *root = cJSON_CreateObject();
    char *out;
    uint16_t length;
    uint8_t buf[256];
    uint8_t i = 0;
    uint16_t index = 0, bytes = 0;
    uint8_t upload_count = count;
    uint8_t upload_map[GPS_BUF_SIZE];

    memset(upload_map, FALSE, GPS_BUF_SIZE);
    memset(buf, 0x00, 256);
    cJSON_AddStringToObject(root, KEY_DEVICE_ID, DEVICE_ID);
    cJSON_AddNumberToObject(root, KEY_MSG_TYPE, MSG_TYPE_MULTI_LOCATION);
    //latitude
    index = 0;
    for(i = 0; i < count; i++) {
        if(msg[i].latitude == 0) {
            upload_count --;
            continue;
        }
        if(i != count - 1) {
            bytes = sprintf((char *)buf + index, "%d,", msg[i].latitude);
        } else {
            bytes = sprintf((char *)buf + index, "%d", msg[i].latitude);
        }
        index += bytes;
        upload_map[i] = TRUE;
    }
    cJSON_AddStringToObject(root, KEY_LAT, buf);
    //longitude
    index = 0;
    for(i = 0; i < count; i++) {
        if(msg[i].longitude == 0) {
            continue;
        }
        if(i != count - 1) {
            bytes = sprintf((char *)buf + index, "%d,", msg[i].longitude);
        } else {
            bytes = sprintf((char *)buf + index, "%d", msg[i].longitude);
        }
        index += bytes;
    }
    cJSON_AddStringToObject(root, KEY_LNG, buf);
    //time
    index = 0;
    for(i = 0; i < count; i++) {
        if(upload_map[i] == FALSE) {
            continue;
        }

        if(msg[i].utc.hour + TIMEZONE >= 24) {
            msg[i].utc.hour -= 24;
            msg[i].utc.date += 1;
            if(msg[i].utc.month == 1 ||
                    msg[i].utc.month == 3 ||
                    msg[i].utc.month == 5 ||
                    msg[i].utc.month == 7 ||
                    msg[i].utc.month == 8 ||
                    msg[i].utc.month == 10 ||
                    msg[i].utc.month == 12) {
                if(msg[i].utc.date > 31) {
                    msg[i].utc.date -= 31;
                    msg[i].utc.month += 1;
                }
            }
            if(msg[i].utc.month == 4 ||
                    msg[i].utc.month == 6 ||
                    msg[i].utc.month == 9 ||
                    msg[i].utc.month == 11) {
                if(msg[i].utc.date > 30) {
                    msg[i].utc.date -= 30;
                    msg[i].utc.month += 1;
                }
            }
            if(msg[i].utc.month == 2) {
                if(msg[i].utc.date > 28) {
                    msg[i].utc.date -= 28;
                    msg[i].utc.month += 1;
                }
            }
            //process year
        }
        msg[i].utc.year = 2017;

        if(i != count - 1) {
            bytes = sprintf((char *)buf + index,
                    "%04d-%02d-%02d %02d:%02d:%02d,",
                    msg[i].utc.year,
                    msg[i].utc.month,
                    msg[i].utc.date,
                    msg[i].utc.hour,
                    msg[i].utc.min,
                    msg[i].utc.sec);
        } else {
            bytes = sprintf((char *)buf + index,
                    "%04d-%02d-%02d %02d:%02d:%02d",
                    msg[i].utc.year,
                    msg[i].utc.month,
                    msg[i].utc.date,
                    msg[i].utc.hour,
                    msg[i].utc.min,
                    msg[i].utc.sec);
        }
        index += bytes;
    }
    cJSON_AddStringToObject(root, KEY_TIME, buf);
    cJSON_AddNumberToObject(root, KEY_LOCATION_COUNT, upload_count);

    out = cJSON_Print(root);
    length = strlen(out);
    if(mLogined) {
        mTransmit.send((uint8_t *)out, length);
    }

    cJSON_Delete(root);
    free(out);
}

void transmit_vin_code(VinCode *code)
{
    cJSON *root = cJSON_CreateObject();
    char *out;
    uint16_t length;
    uint8_t buf[34];
    uint8_t j;
    uint8_t update;
    uint8_t index = 0, bytes = 0;

    cJSON_AddStringToObject(root, KEY_DEVICE_ID, DEVICE_ID);
    cJSON_AddNumberToObject(root, KEY_MSG_TYPE, MSG_TYPE_UPLOAD);
    //combine the data
    transmit_lock();
    memset(buf, 0x00, 34);
    index = 0;
    bytes = 0;
    update = code->updated;
    if(update) {
        for(j = 0; j < 17; j++) {
            bytes = sprintf((char *)buf + index, "%02x", code->code[j]);
            index += bytes;
        }
        //clear update flag
        code->updated = FALSE;
        cJSON_AddStringToObject(root, "eng_data_vin", (char *)buf);
    }
    transmit_unlock();

    out = cJSON_Print(root);
    length = strlen(out);
    mTransmit.send((uint8_t *)out, length);
    //logi("%s: %s", __func__, out);

    cJSON_Delete(root);
    if(out != NULL) {
        free(out);
        out = NULL;
    }
}
uint8_t transmit_eng_data(EngineDataMap *engData)
{
    uint8_t ret;
    cJSON *root = cJSON_CreateObject();
    char *out = NULL;
    uint16_t length;
    uint8_t buf[16];
    uint8_t i, j;
    uint8_t update;
    uint8_t index = 0, bytes = 0;
    uint8_t update_count = 0;

    cJSON_AddStringToObject(root, KEY_DEVICE_ID, DEVICE_ID);
    cJSON_AddNumberToObject(root, KEY_MSG_TYPE, MSG_TYPE_UPLOAD);
    //combine the data
    transmit_lock();
    for(i = 0; i < ENG_DATA_SIZE; i++) {
        memset(buf, 0x00, 16);
        index = 0;
        bytes = 0;
        update = engData->data[i].updated;
        if(update) {
            for(j = 0; j < engData->data[i].len; j++) {
                bytes = sprintf((char *)buf + index, "%02x", engData->data[i].data[j]);
                index += bytes;
            }
            //clear update flag
            engData->data[i].updated = FALSE;
            cJSON_AddStringToObject(root, transmit_get_key(i), (char *)buf);
            update_count ++;
        }
        if(update_count >= UPLOAD_MAX_ITEMS) {
            break;
        }
    }
    transmit_unlock();

    logi("###update_count = %d", update_count);
    if(update_count != 0) {
        out = cJSON_Print(root);
        length = strlen(out);
        mTransmit.send((uint8_t *)out, length);
        //logi("%s: %s", __func__, out);
    }
    cJSON_Delete(root);
    if(out != NULL) {
        free(out);
        out = NULL;
    }
    return (update_count != 0) ? TRUE : FALSE;
}

uint8_t transmit_bcm_data(BcmDataMap *bcmData)
{
    uint8_t ret;
    cJSON *root = cJSON_CreateObject();
    char *out = NULL;
    uint16_t length;
    uint8_t buf[16];
    uint8_t i, j;
    uint8_t update;
    uint8_t index = 0, bytes = 0;
    uint8_t update_count = 0;

    cJSON_AddStringToObject(root, KEY_DEVICE_ID, DEVICE_ID);
    cJSON_AddNumberToObject(root, KEY_MSG_TYPE, MSG_TYPE_UPLOAD);
    //combine the data
    transmit_lock();
    for(i = 0; i < BCM_DATA_SIZE; i++) {
        memset(buf, 0x00, 16);
        index = 0;
        bytes = 0;
        update = bcmData->data[i].updated;
        if(update) {
            for(j = 0; j < bcmData->data[i].len; j++) {
                bytes = sprintf((char *)buf + index, "%02x", bcmData->data[i].data[j]);
                index += bytes;
            }
            //clear update flag
            bcmData->data[i].updated = FALSE;
            cJSON_AddStringToObject(root, transmit_get_bcm_key(i), (char *)buf);
            update_count ++;
        }
        if(update_count >= UPLOAD_MAX_ITEMS) {
            break;
        }
    }
    transmit_unlock();

    logi("###update_count = %d", update_count);
    if(update_count != 0) {
        out = cJSON_Print(root);
        length = strlen(out);
        mTransmit.send((uint8_t *)out, length);
        //logi("%s: %s", __func__, out);
    }

    cJSON_Delete(root);
    if(out != NULL) {
        free(out);
    }
    return (update_count != 0) ? TRUE : FALSE;
}

uint8_t transmit_ipc_data(IpcDataMap *ipcData)
{
    uint8_t ret;
    cJSON *root = cJSON_CreateObject();
    char *out = NULL;
    uint16_t length;
    uint8_t buf[16];
    uint8_t i, j;
    uint8_t update;
    uint8_t index = 0, bytes = 0;
    uint8_t update_count = 0;

    cJSON_AddStringToObject(root, KEY_DEVICE_ID, DEVICE_ID);
    cJSON_AddNumberToObject(root, KEY_MSG_TYPE, MSG_TYPE_UPLOAD);
    //combine the data
    transmit_lock();
    for(i = 0; i < IPC_DATA_SIZE; i++) {
        memset(buf, 0x00, 16);
        index = 0;
        bytes = 0;
        update = ipcData->data[i].updated;
        if(update) {
            for(j = 0; j < ipcData->data[i].len; j++) {
                bytes = sprintf((char *)buf + index, "%02x", ipcData->data[i].data[j]);
                index += bytes;
            }
            //clear update flag
            ipcData->data[i].updated = FALSE;
            cJSON_AddStringToObject(root, transmit_get_ipc_key(i), (char *)buf);
            update_count ++;
        }
        if(update_count >= UPLOAD_MAX_ITEMS) {
            break;
        }
    }
    transmit_unlock();

    if(update_count != 0) {
        out = cJSON_Print(root);
        length = strlen(out);
        mTransmit.send((uint8_t *)out, length);
        //logi("%s: %s", __func__, out);
    }

    cJSON_Delete(root);
    if(out != NULL) {
        free(out);
    }
    return (update_count != 0) ? TRUE : FALSE;
}

void transmit_codes_data(FaultCodeItem *item)
{
    uint8_t key_tables[CODE_SIZE][16] =
    {
        KEY_ENGINE_CODE,
        KEY_AT_CODE,
        KEY_ABS_CODE,
        KEY_SRS_CODE,
        KEY_BCM_CODE,
        KEY_IPC_CODE,
        KEY_EPS_CODE,
        KEY_AC_CODE,
        KEY_TPMS_CODE,
    };
    cJSON *root;
    char *out;
    uint16_t length;
    uint8_t buf[128];
    uint8_t index = 0, bytes = 0;
    uint8_t i;

    if(item->count == 0)
        return;
    root = cJSON_CreateObject();

    cJSON_AddStringToObject(root, KEY_DEVICE_ID, DEVICE_ID);
    cJSON_AddNumberToObject(root, KEY_MSG_TYPE, MSG_TYPE_FAULT_CODE);
    memset(buf, 0x00, 128);
    for(i = 0; i < item->count; i++) {
        if(i != item->count - 1) {
            bytes = sprintf((char *)buf + index, "%04x,", item->codes[i]);
        } else {
            bytes = sprintf((char *)buf + index, "%04x", item->codes[i]);
        }
        index += bytes;
    }
    cJSON_AddStringToObject(root, key_tables[item->type], buf);

    out = cJSON_Print(root);
    length = strlen(out);
    mTransmit.send((uint8_t *)out, length);
    cJSON_Delete(root);
    free(out);
}

void transmit_lock(void)
{
    OS_ERR err;

    //    logi("++%s++", __func__);
    OSMutexPend(&mTransmit.mutex, 0, OS_OPT_PEND_BLOCKING, 0, &err);
}

void transmit_unlock(void)
{
    OS_ERR err;

    //    logi("--%s--", __func__);
    OSMutexPost(&mTransmit.mutex, OS_OPT_POST_NONE, &err);
}

char* transmit_get_key(uint8_t i)
{
    return pidList[i].key;
}

char* transmit_get_bcm_key(uint8_t i)
{
    return bcmList[i].key;
}

char* transmit_get_ipc_key(uint8_t i)
{
    return ipcList[i].key;
}

char *transmit_get_deviceid(void)
{
    memcpy(mDeviceId + 1, l206_get_imei(), 15);
    mDeviceId[0] = '8';
    mDeviceId[16] = '\0';
    return mDeviceId;
}

void transmit_update_deviceid(void)
{
}

